package io.tolgee.service.dataImport.processors

import io.tolgee.dtos.dataImport.ImportFileDto
import io.tolgee.exceptions.ImportCannotParseFileException
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.util.zip.ZipException
import java.util.zip.ZipInputStream

class ZipTypeProcessor(
  private val maxUncompressedBytes: Long,
) : ImportArchiveProcessor {
  companion object {
    val IGNORE_PREFIXES = arrayOf("__", ".")
  }

  override fun process(file: ImportFileDto): Collection<ImportFileDto> {
    try {
      return readFiles(file.data.inputStream())
    } catch (e: ZipException) {
      throw ImportCannotParseFileException(file.name, e.message, e)
    } catch (e: IOException) {
      throw ImportCannotParseFileException(file.name, e.message, e)
    } catch (e: MaxSizeExceededException) {
      throw ImportCannotParseFileException(file.name, e.message, e)
    }
  }

  private fun readFiles(archive: InputStream): Collection<ImportFileDto> {
    var totalSize: Long = 0
    // .zip archives generated by macOS compress feature return duplicities
    // I am removing them here by adding the files to the map
    val files = mutableMapOf<String, ImportFileDto>()

    ZipInputStream(archive).use { zipInputStream ->
      while (true) {
        val entry = zipInputStream.nextEntry ?: break
        if (entry.isDirectory) {
          continue
        }

        val fileName = entry.name.replaceRootSlash()
        if (files.contains(fileName)) {
          continue
        }
        if (IGNORE_PREFIXES.any { fileName.startsWith(it) }) {
          continue
        }

        val data = readFile(zipInputStream, maxUncompressedBytes - totalSize)

        if (data.isEmpty()) {
          continue
        }

        totalSize += data.size

        files[fileName] =
          ImportFileDto(
            name = fileName,
            data = data,
          )
      }
    }

    return files.values.sortedBy { it.name }
  }

  private fun readFile(
    archive: ZipInputStream,
    maxSize: Long,
  ): ByteArray {
    var size: Long = 0

    val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
    val baos = ByteArrayOutputStream()
    while (true) {
      val read = archive.read(buffer)
      if (read == -1) break
      size += read
      if (size > maxSize) {
        throw MaxSizeExceededException()
      }
      baos.write(buffer, 0, read)
    }

    return baos.toByteArray()
  }

  private fun String.replaceRootSlash() = removePrefix("/")

  class MaxSizeExceededException : Exception("Archive exceeds maximum allowed size")
}
